package utstat;

import static utstat.Type.*;


import java.io.BufferedReader;
import java.io.Closeable;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.StringTokenizer;
import static utstat.Team.*;

public class UtParser {

    private List<UtEventListenerRunner> listenerRunners = new ArrayList<UtEventListenerRunner>();

    private int currentRound;

    private String currentMap;

    private Map<Integer, String> currentPlayer;
    
    private Map<String,Team> playerTeams;

    private Map<Integer, Weapon> hitTypes;

    private Map<Integer, Weapon> killTypes;

    public UtParser() {
        hitTypes = loadDataFile("data/hitweapons.txt");
        killTypes = loadDataFile("data/killweapons.txt");
    }

	public void parse(String filename) throws IOException {
		BufferedReader br = null;
		long startBacklog = System.currentTimeMillis();
		try {
			br = new BufferedReader(new FileReader(filename));
			String line = br.readLine();
			/// parse backlog for at least 2 seconds
			while(System.currentTimeMillis()-startBacklog<2000)
			while (line != null) {
				parseLine(line);
				line = br.readLine();
			}
			System.out.println("read backlog");
			
			Rcon.goLive();
			while (true) {
				line = br.readLine();
				
				if (line != null){
					System.out.println(line);
					parseLine(line);}
				else
					try {
						Thread.sleep(50);
					} catch (InterruptedException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}

			}
		} finally {
			close(br);
		}
    }

    private void parseLine(String line) {
        if (line == null || line.length() < 7) {
            return;
        }
        int time = parseTime(line.substring(0, 7));
        line = line.substring(7);
        if (line.startsWith("Kill:")) {
            parseKill(line.substring(6), time);
        } else if (line.startsWith("Hit:")) {
            parseHit(line.substring(5), time);
        } else if (line.startsWith("InitGame:")) {
            parseInitGame(line.substring(10));
        } else if (line.startsWith("ShutdownGame:")) {
            // parseShutdownGame(time);
        } else if (line.startsWith("ClientConnect:")) {
            // parseClientConnect(line.substring(15), time);
        } else if (line.startsWith("ClientUserinfo:")) {
            parseClientUserinfo(line.substring(16));
        } else if (line.startsWith("ClientUserinfoChanged:")) {
             parseClientUserinfoChanged(line.substring(23), time);
        } else if (line.startsWith("ClientDisconnect:")) {
             parseClientDisconnect(line.substring(18), time);
        } else if (line.startsWith("score:")) {
            parseScore(line.substring(7), time);
        } else if (line.startsWith("Item:")) {
            parseItem(line.substring(6), time);
        } else if (line.startsWith("Flag:")) {
            parseFlag(line.substring(6), time);
        } else if (line.startsWith("Flag Return:")) {
            parseFlagReturn(line.substring(13), time);
        }else if (line.startsWith("say:")) {
            parseSay(line.substring(5), time);
        }
    }
 

	private void parseSay(String line, int time) {

		StringTokenizer st = new StringTokenizer(line);
		st.nextToken();
		String src = st.nextToken();
		String message = line.substring(line.indexOf(": ") + 2);
		if (message.toLowerCase().startsWith("balance")) {
			System.out.println("balance1");
			sendEvent(new UtEvent(src, message.toLowerCase()));
		}

	}

	private void parseScore(String line, int time) {
        try {
            StringTokenizer st = new StringTokenizer(line);
            int score = Integer.parseInt(st.nextToken());
            st.nextToken();// Drop ping label
            st.nextToken();// Drop ping value
            st.nextToken();// Drop client label
            int player = Integer.parseInt(st.nextToken());
            Team team = playerTeams.get(currentPlayer.get(player));
            sendEvent(new UtEvent(time, Type.score, currentPlayer.get(player),
                    score, currentRound, currentMap, team, playerTeams));

        } catch (NumberFormatException e) {
            System.err.println("parseScore: Could not parse score!: " + line);
        }
    }

    private void parseKill(String line, int time) {
        StringTokenizer st = new StringTokenizer(line, ": ");
        try {
            int src = Integer.parseInt(st.nextToken());
            int dest = Integer.parseInt(st.nextToken());
            int weapon = Integer.parseInt(st.nextToken());
            String player = currentPlayer.get(src);
            Team team = playerTeams.get(player);
            sendEvent(new UtEvent(time, kill, player, currentPlayer.get(dest), killTypes.get(weapon), null,
                    currentRound, currentMap, team, playerTeams));
        } catch (NumberFormatException e) {
            System.err.println("parseKill: Could not parse int: " + line);
        }
    }

    private void parseHit(String line, int time) {
        StringTokenizer st = new StringTokenizer(line, ": ");
        try {
            int dest = Integer.parseInt(st.nextToken());
            int src = Integer.parseInt(st.nextToken());
            int bodyPart = Integer.parseInt(st.nextToken());
            int weapon = Integer.parseInt(st.nextToken());
            Team team = playerTeams.get(currentPlayer.get(src));
            sendEvent(new UtEvent(time, hit, currentPlayer.get(src),
                    currentPlayer.get(dest), hitTypes.get(weapon),
                    BodyPart.values()[bodyPart], currentRound, currentMap, team,playerTeams));
        } catch (NumberFormatException e) {
            System.err.println("parseKill: Could not parse int: " + line);
        }
    }
    
    // auto flag return only
	private void parseFlagReturn(String line, int time) {
		if(line.equals("RED")||line.equals("BLUE")) {
			sendEvent(new UtEvent(time, flagReturn, null, line.equals("RED")?red:blue, currentRound, currentMap));
					
		}}
    
	private void parseFlag(String line, int time) {

		StringTokenizer st = new StringTokenizer(line, ": ");
		try {
			int src = Integer.parseInt(st.nextToken());
			int action = Integer.parseInt(st.nextToken());
			String flag = st.nextToken();
			
			Type type = action == 1 ? flagReturn : action == 2 ? flagCapture : action == 0 ? flagDrop : null;;
			if (type == null) return;
			Team team = null;
            if(flag.equals("team_CTF_redflag")) team = red;
            else if(flag.equals("team_CTF_blueflag")) team = blue;
            else return;
			UtEvent event = new UtEvent(time, type, currentPlayer.get(src), team, currentRound, currentMap);
			sendEvent(event);
		} catch (NumberFormatException e) {
			System.err.println("parseFlag: Could not parse int: " + line);
		}
	}
	 // only interested in flag pickups
    private void parseItem(String line, int time) {
            StringTokenizer st = new StringTokenizer(line, ": ");
        try {
        	int src = Integer.parseInt(st.nextToken());
            String item = st.nextToken();
            Team team = null;
            if(item.equals("team_CTF_redflag")) team = red;
            else if(item.equals("team_CTF_blueflag")) team = blue;
            else return;
            UtEvent event = new UtEvent(time, flagGrab, currentPlayer.get(src), team, currentRound, currentMap);
			sendEvent(event);
        } catch (NumberFormatException e) {
            System.err.println("parseItem: Could not parse int: " + line);
        }
    }
    
    private void parseInitGame(String line) {
        currentPlayer = new HashMap<Integer, String>();
        playerTeams = new HashMap<String, Team>();
        currentRound++;
        int i = line.indexOf("mapname\\") + 8;
        int j = line.indexOf("\\", i);
        currentMap = line.substring(i, j);
    }

    private void parseClientUserinfo(String line) {
        try {
            StringTokenizer st = new StringTokenizer(line);
            String playerIdString = st.nextToken();
            int id = Integer.parseInt(playerIdString);
            String playerInfo = line.substring(playerIdString.length() + 1);
            Map<String, String> userInfo = parseUserinfoString(playerInfo);
            currentPlayer.put(id, userInfo.get("name"));
        } catch (NumberFormatException e) {
            System.err.println("parseClientUserinfo: Could not identify player id: "
                    + line);
        }
    }

	private void parseClientUserinfoChanged(String line, int time) {
		try {
			StringTokenizer st = new StringTokenizer(line, " \\:");
			Integer clientNumber = Integer.parseInt(st.nextToken());

			Team team = null;
			while (st.hasMoreElements()) {
				String key = st.nextToken();
				String value = st.nextToken();
				if (key.equals("t")) {
					if (value.equals("1")) team = red;
					if (value.equals("2")) team = blue;
					if (value.equals("3")) team = spectator;
					break; // don't need any values after t is found.
				}
			}

			playerTeams.put(currentPlayer.get(clientNumber), team); 
			
			sendEvent(new UtEvent(time, Type.teamChange, currentPlayer.get(clientNumber),
                    -1, currentRound, currentMap, team, playerTeams));
		} catch (NumberFormatException e) {
			System.err.println("parseClientUserinfoChanged: Could not identify player id: " + line);
		}
	}
	
	   private void parseClientDisconnect(String line, int time) {
		   StringTokenizer st = new StringTokenizer(line);
		   Integer clientNumber = Integer.parseInt(st.nextToken());
		   playerTeams.remove(currentPlayer.get(clientNumber));
		   currentPlayer.remove(clientNumber);
	   }

    private Map<String, String> parseUserinfoString(String playerInfo) {
        StringTokenizer playerInfoTokenizer = new StringTokenizer(playerInfo,
                "\\");
        Map<String, String> info = new HashMap<String, String>();
        while (playerInfoTokenizer.hasMoreTokens()) {
            String key = playerInfoTokenizer.nextToken();
            if (playerInfoTokenizer.hasMoreTokens()) {
                info.put(key, playerInfoTokenizer.nextToken());
            }
        }
        return info;
    }

    private int parseTime(String time) {
        int index = time.indexOf(':');
        if (index < 0) {
            System.err.println("Invalid time: " + time);
            return 0;
        }
        int mins = 0;
        try {
            mins = Integer.parseInt(time.substring(0, index).trim());
        } catch (NumberFormatException e) {
            System.err.println("Invalid minutes: "
                    + time.substring(0, index).trim());
        }
        int seconds = 0;
        try {
            seconds = Integer.parseInt(time.substring(index + 1).trim());
        } catch (NumberFormatException e) {
            System.err.println("Invalid time: "
                    + time.substring(index + 1).trim());
        }
        return seconds + 60 * mins;
    }

    public static void close(Closeable closeable) {
        if (closeable != null) {
            try {
                closeable.close();
            } catch (IOException e) {
                // Silently eat...
            }
        }
    }

    private static Map<Integer, Weapon> loadDataFile(String name) {
        Map<Integer, Weapon> map = new HashMap<Integer, Weapon>();
        BufferedReader br = null;
        try {
            br = new BufferedReader(new FileReader(name));
            String line = br.readLine();
            while (line != null && line.length() > 0) {
                line = line.trim();
                StringTokenizer st = new StringTokenizer(line);
                String id = st.nextToken();
                line = line.substring(id.length());
				if (!line.equals("")) {
					Weapon weapon = Weapon.valueOf(Weapon.class, line.trim().replaceAll("[ -]", ""));
					map.put(Integer.parseInt(id), weapon);
				}
				line = br.readLine();
            }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            close(br);
        }
        return map;
    }

    private void sendEvent(UtEvent ute) {
        for (UtEventListenerRunner listenerRunner : listenerRunners) {
            listenerRunner.enqueueEvent(ute);
        }
    }

    public void registerListener(UtEventListener listener) {
        UtEventListenerRunner listenerRunner = new UtEventListenerRunner(
                listener);
        listenerRunners.add(listenerRunner);
        new Thread(listenerRunner).start();
    }

}
